package com.nothing.controller;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Controller;

import com.arcsoft.face.FaceInfo;
import com.nothing.config.StageManager;
import com.nothing.domain.UserFaceGroup;
import com.nothing.domain.UserFaceInfo;
import com.nothing.dto.FaceSearchResult;
import com.nothing.dto.WebCamInfo;
import com.nothing.service.FaceEngineService;
import com.nothing.service.UserFaceInfoService;
import com.nothing.view.FxmlView;

import cn.hutool.core.collection.CollectionUtil;
import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;

/**
 * 调用网络摄像头
 * 
 * @author shíQíang㊚
 */
@Controller
public class OpenCVPreviewController implements Initializable { 
	 
	@FXML
	Button btnStartCamera;
	@FXML
	Button btnStopCamera;
	@FXML
	Button btnDisposeCamera;
	@FXML
	Button btnBackToUser;
	@FXML
	Button btnCheck;
	@FXML
	Button takePicture;
	@FXML
	ComboBox<WebCamInfo> cbCameraOptions;
	@FXML
	BorderPane bpWebCamPaneHolder;
	@FXML
	FlowPane fpBottomPane;
	@FXML
	ImageView imgWebCamCapturedImage;
	@FXML
	ImageView faceImage;
	@FXML
	ComboBox<UserFaceGroup> groupOptions;

	@Autowired
	FaceEngineService faceEngineService;

	@Autowired
	UserFaceInfoService userFaceInfoService;

	@Lazy
	@Autowired
	private StageManager stageManager;

	private ExecutorService executorService = Executors.newFixedThreadPool(1);
	private ExecutorService executorCamService = Executors.newFixedThreadPool(1);
	private final ExecutorService pool = Executors.newFixedThreadPool(1, new ThreadFactory() {  
        public Thread newThread(Runnable r) {  
            Thread thread = new Thread(r);  
            thread.setDaemon(true);  
            return thread;  
        }  
    }); 

	private Long groupId = 1L;
	private FFmpegFrameGrabber grabber = null;
	private boolean stopCamera = false;
	private ObjectProperty<Image> imageProperty = new SimpleObjectProperty<Image>();
	private ObjectProperty<Image> faceProperty = new SimpleObjectProperty<Image>();
	
	private LinkedBlockingQueue<BufferedImage> queue = new LinkedBlockingQueue<>(10);
	
	private String cameraListPromptText = "选择摄像头";
	static Map<String,String> webCams = null;
	
	static{
		webCams = new HashMap<String,String>(){
			private static final long serialVersionUID = 1L;{ 
			put("安检摄像头","http://admin:admin@192.168.0.16:8181"); 
		 }};  
	}
	
	@Override
	public void initialize(URL arg0, ResourceBundle arg1) {

		fpBottomPane.setDisable(true);
		btnBackToUser.setDisable(false);
		ObservableList<WebCamInfo> options = FXCollections.observableArrayList(); 
		 
		for (String webcam : webCams.keySet()) {
			options.add(WebCamInfo.builder().webCamIndex(webCams.get(webcam)).webCamName(webcam).build());
		}
		
		cbCameraOptions.setItems(options);
		cbCameraOptions.setPromptText(cameraListPromptText);
		cbCameraOptions.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<WebCamInfo>() {
			@Override
			public void changed(ObservableValue<? extends WebCamInfo> arg0, WebCamInfo old, WebCamInfo newCam) {
				if (newCam != null) {
					System.out.println("摄像头索引：" + newCam.getWebCamIndex() + ": 摄像头名称:" + newCam.getWebCamIndex());
					initializeWebCam(newCam.getWebCamIndex());
				}
			}
		});

		ObservableList<UserFaceGroup> groupOption = FXCollections.observableArrayList(
				UserFaceGroup.builder().id(1L).name("特殊人").build(), UserFaceGroup.builder().id(2L).name("普通人").build());
		groupOptions.setItems(groupOption);
		groupOptions.setPromptText("选择分组");
		groupOptions.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<UserFaceGroup>() {
			@Override
			public void changed(ObservableValue<? extends UserFaceGroup> arg0, UserFaceGroup old,
					UserFaceGroup newGroup) {
				if (newGroup != null) {
					System.out.println("所属组: " + newGroup.getName());
					initializeGroup(newGroup);
				}
			}
		});

		Platform.runLater(new Runnable() {
			@Override
			public void run() {
				setImageViewSize();
			}
		});
		ExecutorService serviceCheck = Executors.newCachedThreadPool();
		// 启动线程
		isRunning = true;
		serviceCheck.execute(new CheckFace(queue));

	}

	protected void setImageViewSize(){
		
		double height = imgWebCamCapturedImage.getFitHeight();
		double width = imgWebCamCapturedImage.getFitWidth();
		imgWebCamCapturedImage.setFitHeight(height);
		imgWebCamCapturedImage.setFitWidth(width);
		imgWebCamCapturedImage.prefHeight(height);
		imgWebCamCapturedImage.prefWidth(width);
		imgWebCamCapturedImage.setPreserveRatio(true);
		
		height = faceImage.getFitHeight();
		width = faceImage.getFitWidth();
		faceImage.setFitHeight(height);
		faceImage.setFitWidth(width);
		faceImage.prefHeight(height);
		faceImage.prefWidth(width);
		faceImage.setPreserveRatio(true);
	}

	protected void initializeGroup(final UserFaceGroup webCamIndex) {
		groupId = webCamIndex.getId();
	}

	protected void initializeWebCam(final String webCamIndex) {

		Task<Void> webCamIntilizer = new Task<Void>() {

			@Override
			protected Void call() throws Exception {
				try {
					stopCamera = true;
					Thread.sleep(1000);
					closeCamera(); 
					Thread.sleep(1000);
					grabber = FFmpegFrameGrabber.createDefault(webCamIndex); 
			        grabber.setOption("rtsp_transport", "tcp"); // 使用tcp的方式，不然会丢包很严重
			        grabber.start();
					startWebCamStream(); 
				} catch (Exception e) {
					
				}
				return null;
			} 
		}; 
		if(executorCamService.isShutdown()){
			executorCamService = Executors.newFixedThreadPool(1);
		}
		executorCamService.execute(webCamIntilizer);
		fpBottomPane.setDisable(false);
		btnStartCamera.setDisable(true);
	}

	protected void startWebCamStream() {
		
		
		stopCamera = false;
		Task<Void> task = new Task<Void>() {

			@Override
			protected Void call() throws Exception {

				final AtomicReference<WritableImage> ref = new AtomicReference<>();
				BufferedImage img = null;
				Java2DFrameConverter conver = new Java2DFrameConverter();
				while (!stopCamera) {
					try { 
						Frame frame = null;
						try {
							frame = grabber.grabImage();
						} catch (Exception e){
							if(grabber != null){
								grabber.close();
							} 
							frame = null;
						} 
						if(frame == null){
							continue;
						}
						if((img = conver.convert(frame)) != null) {
 
							List<FaceInfo> faces = faceEngineService.detectFaces(img);
							if(CollectionUtil.isNotEmpty(faces)){ 
								
								if(toCheck){ 
									BufferedImage bimage2 = new BufferedImage(img.getWidth(), img.getHeight(), img.getType());
							        bimage2.setData(img.getData());
							        queue.put(bimage2); 
								}
								if(toTakePicture) {
									BufferedImage bimage2 = new BufferedImage(img.getWidth(), img.getHeight(), img.getType());
							        bimage2.setData(img.getData());
									pool.execute(new TakeFace(bimage2));
								} 
								img = faceEngineService.drowFace(img);
								 
							}

							ref.set(SwingFXUtils.toFXImage(img, ref.get()));
							img.flush();  
							Platform.runLater(new Runnable() {

								@Override
								public void run() { 
									imageProperty.set(ref.get()); 
								}
							}); 
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				return null;
			}
		};
		if(executorService.isShutdown()){
			executorService = Executors.newFixedThreadPool(1);
		}
		executorService.execute(task);
		imgWebCamCapturedImage.imageProperty().bind(imageProperty);
		faceImage.imageProperty().bind(faceProperty);

	}

	class TakeFace implements Runnable{
		
		private BufferedImage img;
		 
		public TakeFace(BufferedImage img){
			this.img = img; 
		}
		
		@Override
		public void run() {
			try {
				WritableImage hasCheckedFace = SwingFXUtils.toFXImage(img, null);
				if (toTakePicture && hasCheckedFace != null) {
					toTakePicture = false;
					imageProperty.set(hasCheckedFace);
					showPicture(hasCheckedFace);
				}
			} catch (Exception e){ 
				e.printStackTrace();
			}
		}
		
	}
	
	boolean  isRunning = true;
	class CheckFace implements Runnable{
		
		private LinkedBlockingQueue<BufferedImage> queue;//阻塞队列
		 
		public CheckFace(LinkedBlockingQueue<BufferedImage> queue){
			this.queue = queue;
		}
		
		@Override
		public void run() {
			try {
				while (isRunning) {
					System.out.println("正从队列获取数据...");
					BufferedImage data = queue.poll(2, TimeUnit.SECONDS);//有数据时直接从队列的队首取走，无数据时阻塞，在2s内有数据，取走，超过2s还没数据，返回失败
	                if (null != data) { 
	                     System.out.println("正在消费数据：");
	                     List<FaceSearchResult> usersInDb = faceEngineService.findUsersByFaces(data, groupId); 
	     				 WritableImage hasCheckedFace = SwingFXUtils.toFXImage(data, null);
	     				 if (toCheck && hasCheckedFace != null 
	     						&& CollectionUtil.isNotEmpty(usersInDb)) {
	     					toCheck = false;
	     					faceProperty.set(hasCheckedFace);
	     					checkSuccess(usersInDb, hasCheckedFace);
	     				}
	     				data.flush(); 
	                }else{
	                	System.out.println("队列中无数据...");
	                }
				}
			} catch (Exception e){ 
				e.printStackTrace();
			} 
		}
		
	}
	

	private boolean toCheck = false;

	private void checkSuccess(List<FaceSearchResult> users, Image findImg){ 
		Platform.runLater(new Runnable(){ 
			@Override
			public void run() {
				String name = "";
				String age = "";
				String gender = "";
				String group = ""; 
				
				if (CollectionUtil.isNotEmpty(users)) {
					for (FaceSearchResult user : users) {
						UserFaceInfo userInDb = userFaceInfoService.findByFaceId(user.getFaceId());
						name += userInDb.getName() + "|";
						age += userInDb.getAge() + "|";
						gender += (userInDb.getGender().equals("0") ? "女" : "男") + "|";
						group +=  " "+"|";
					}
				} 
				
				Stage stage = new Stage();

				FXMLLoader loader = new FXMLLoader(getClass().getResource(FxmlView.SHOW.getFxmlFile()));

				loader.setController(new ShowCheckFaceController(findImg,name,age,gender,group));

				try {
					
					stage.setScene(new Scene(loader.load()));
					stage.setTitle(FxmlView.SHOW.getTitle());
					stage.show();
					
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		});
		
	}
	protected void showPicture(Image image) {
		Platform.runLater(new Runnable(){ 
			@Override
			public void run() {
				Stage stage = new Stage();
		
				FXMLLoader loader = new FXMLLoader(getClass().getResource(FxmlView.TAKE.getFxmlFile()));
		
				loader.setController(new TakeFaceController(image, userFaceInfoService));
		
				try {
					
					stage.setScene(new Scene(loader.load()));
					stage.setTitle(FxmlView.TAKE.getTitle());
					stage.show();
					
				} catch (IOException e) {
					e.printStackTrace();
				} 
			}
		}); 
	}

	private void closeCamera() throws Exception {
		stopCamera = true;
		if (grabber != null) {
			grabber.close();
		}
		try {
			executorService.shutdown(); 
		} catch (Exception e) {
			executorService.shutdownNow();
		}
		try {
			executorCamService.shutdown();
		} catch (Exception e) {
			executorCamService.shutdownNow();
		} 
	}

	public void stopCamera(ActionEvent event) {
		stopCamera = true;
		btnStartCamera.setDisable(false);
		btnStopCamera.setDisable(true);
	}

	public void startCamera(ActionEvent event) {
		stopCamera = false;
		startWebCamStream();
		btnStartCamera.setDisable(true);
		btnStopCamera.setDisable(false);
	}

	public void disposeCamera(ActionEvent event) throws Exception {
		stopCamera = true;
		closeCamera();
		btnStopCamera.setDisable(true);
		btnStartCamera.setDisable(true);
	}

	public void backToUser(ActionEvent event) throws Exception {
		stopCamera = true;
		isRunning = false;
		Thread.sleep(4000);
		closeCamera();
		stageManager.switchScene(FxmlView.USER);
	}

	public void changeFlag(ActionEvent event) {
		toCheck = true;
	}

	private boolean toTakePicture = false;

	public void savePicture(ActionEvent event) {
		toTakePicture = true;
	} 
}
